#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include <string>
#include <memory>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include "log.hpp"

#define SIZE 1024 // 服务端缓冲区大小




//////演示三：简易版聊天系统（多线程模式）
class UdpServer
{
public:
    // 构造:将对应的端口号、IP传入
    UdpServer(uint16_t port, std::string ip = "") // 对ip默认值说明：方便后续bind操作，1、可从任意IP获取数据(默认情况)，2、也可指定需要的IP（自己传入参数的情况）
        : port_(port), ip_(ip), sock_(-1)
    {
    }

    // 析构：关闭套接字
    ~UdpServer()
    {
        if (sock_ >= 0)
            close(sock_);
    }

    // 初始化服务器
    bool InitServer()
    {
        // 1、创建套接字:此处AF_INET也可以是FP_INET
        sock_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock_ < 0)
        {
            logMessage(ERROR, "%d:%s ", errno, strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "创建套接字成功, sock: %d ", sock_);

        // 2、bind绑定：
        // 2.1、绑定前的准备工作：
        struct sockaddr_in localaddr;
        bzero(&localaddr, sizeof localaddr); // 将结构体清零
        localaddr.sin_family = AF_INET; // 告知通讯方式，通常与domain同
        localaddr.sin_port = htons(port_); // 端口号：注意转为网络字节序
        localaddr.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // IP：点分十进制->四字节+网络字节序
        // 2.2、绑定：
        if (bind(sock_, (struct sockaddr *)&localaddr, sizeof localaddr) < 0) // 注意对localaddr类型转换
        {
            logMessage(ERROR, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "绑定成功，初始化服务器完成！");

        return true;
    }

    // ////2、测试二：演示指令执行
    // 启动服务器
    void Start()
    {

        // 1、作为一款网络服务器，永远不退出地在接收客服端通过网络传来的请求
        while (true)
        {
            // 1.1、准备工作：
            // a、用于后续从网络中读取客户端的IP、端口号
            struct sockaddr_in clientaddr;
            bzero(&clientaddr, sizeof clientaddr);
            socklen_t len = sizeof clientaddr; // 输入输出型参数：输入时传递的是clientaddr当前定义的大小，使用是会输出实际大小
            // b、用于recvfrom时存储数据
            char server_buffer[SIZE] = "0";
            // c、用于业务处理时，记录当前发送消息的客户端：ip-prot（unordered_map的key值）
            char key[64] = "0";

            // 1.2、读取数据：
            ssize_t s = recvfrom(sock_, server_buffer, sizeof(server_buffer) - 1, 0, (struct sockaddr *)&clientaddr, &len); // 注意对clientaddr类型转换
       
           
            if (s > 0) // 1.3、读取成功，进行业务处理
            {
                server_buffer[s] = 0;

                // a-1、获取客户端端口号、IP：因为是从网络中获取，这里本地显示时需要转换字节序和风格
                uint16_t client_port = ntohs(clientaddr.sin_port);
                std::string client_ip = inet_ntoa(clientaddr.sin_addr);

                // a-2:检测本次发送消息的客户端是否存在_users中，若无，则需要添加进来
                snprintf(key, sizeof key, "[%s-%d]", client_ip.c_str(), client_port);
                logMessage(DEBUG, "key:%s send a massage to server.", key);
                auto ifexist = _users.find(key); // iterator find ( const key_type& k );
                if(ifexist ==_users.end())// unordered_map::end 
                {
                    // 说明_users中没有记录该客户端，需要插入
                    _users.insert({key, clientaddr});
                    logMessage(DEBUG, "add a new user: %s .", key);
                }
                //////////////////////////////////////////////////////////////////

            }
            // 2、响应：将处理好的结果返回。
            // c-1:遍历_users，挨个推送消息
            for (auto &iter : _users)
            {
                // 传回格式： ip-prot# XXXXXX ，例如：[127.0.0.1-8080]# 你好！
                std::string respond = key;
                respond += "# ";
                respond += server_buffer;
                logMessage(DEBUG,"push message to: %s .",key);
                sendto(sock_, respond.c_str(), respond.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));
            }
        }
    }



private:
    uint16_t port_;  // 端口号：16位的整数
    std::string ip_; // IP地址：点分十进制字符串风格
    int sock_;       // 通讯时的套接字：需要供多处使用
    std::unordered_map<std::string, struct sockaddr_in> _users;
    //用于记录连接上服务器的所有客户端：[ip-prot,sockaddr结构体]，即[字符串信息显示的IP和端口号,实际用于获取IP和端口号的结构]
};




////////////////////////////////////////////////////////////////
//////演示一、演示二相关代码：
namespace Udp_test1_2
{
    class UdpServer
{
public:
    // 构造:将对应的端口号、IP传入
    UdpServer(uint16_t port, std::string ip = "") // 对ip默认值说明：方便后续bind操作，1、可从任意IP获取数据(默认情况)，2、也可指定需要的IP（自己传入参数的情况）
        : port_(port), ip_(ip), sock_(-1)
    {
    }

    // 析构：关闭套接字
    ~UdpServer()
    {
        if (sock_ >= 0)
            close(sock_);
    }

    // 初始化服务器
    bool InitServer()
    {
        // 1、创建套接字:此处AF_INET也可以是FP_INET
        sock_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock_ < 0)
        {
            logMessage(ERROR, "%d:%s ", errno, strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "创建套接字成功, sock: %d ", sock_);

        // 2、bind绑定：
        // 2.1、绑定前的准备工作：
        struct sockaddr_in localaddr;
        bzero(&localaddr, sizeof localaddr); // 将结构体清零
        localaddr.sin_family = AF_INET; // 告知通讯方式，通常与domain同
        localaddr.sin_port = htons(port_); // 端口号：注意转为网络字节序
        localaddr.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // IP：点分十进制->四字节+网络字节序
        // 2.2、绑定：
        if (bind(sock_, (struct sockaddr *)&localaddr, sizeof localaddr) < 0) // 注意对localaddr类型转换
        {
            logMessage(ERROR, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "绑定成功，初始化服务器完成！");

        return true;
    }


    ////1、测试一：演示echo版服务器
    // 启动服务器
    void Start_1()
    {

        // 1、作为一款网络服务器，永远不退出地在接收客服端通过网络传来的请求
        while (true)
        {
            // 1.1、准备工作：
            // a、用于后续从网络中读取客户端的IP、端口号
            struct sockaddr_in clientaddr;
            bzero(&clientaddr, sizeof clientaddr);
            socklen_t len = sizeof clientaddr; // 输入输出型参数：输入时传递的是clientaddr当前定义的大小，使用是会输出实际大小
            // b、用于存储数据
            char server_buffer[SIZE];

            // 1.2、读取数据：
            ssize_t s = recvfrom(sock_, server_buffer, sizeof(server_buffer) - 1, 0, (struct sockaddr *)&clientaddr, &len); // 注意对clientaddr类型转换
            if (s > 0) // 读取成功
            {
                server_buffer[s] = 0;

                // a、获取客户端端口号、IP：因为是从网络中获取，这里本地显示时需要转换字节序和风格
                uint16_t client_port = ntohs(clientaddr.sin_port);
                std::string client_ip = inet_ntoa(clientaddr.sin_addr);
                printf("[%s:%d]# %s\n", client_ip.c_str(), client_port, server_buffer);

                // b、处理客服端发来的数据请求(自定义TODO)
            }
            // 2、响应：将处理好的结果返回。
            sendto(sock_, server_buffer, strlen(server_buffer), 0, (struct sockaddr *)&clientaddr, len);
            
        }
    }




    // ////2、测试二：演示指令执行
    // 启动服务器
    void Start()
    {

        // 1、作为一款网络服务器，永远不退出地在接收客服端通过网络传来的请求
        while (true)
        {
            // 1.1、准备工作：
            // a、用于后续从网络中读取客户端的IP、端口号
            struct sockaddr_in clientaddr;
            bzero(&clientaddr, sizeof clientaddr);
            socklen_t len = sizeof clientaddr; // 输入输出型参数：输入时传递的是clientaddr当前定义的大小，使用是会输出实际大小
            // b、用于存储数据
            char server_buffer[SIZE];

            // 1.2、读取数据：
            ssize_t s = recvfrom(sock_, server_buffer, sizeof(server_buffer) - 1, 0, (struct sockaddr *)&clientaddr, &len); // 注意对clientaddr类型转换
            std::string respond;//用于业务处理后，将结果sendto发送给客户端
           
            if (s > 0) // 读取成功
            {
                server_buffer[s] = 0;

                // a、获取客户端端口号、IP：因为是从网络中获取，这里本地显示时需要转换字节序和风格
                uint16_t client_port = ntohs(clientaddr.sin_port);
                std::string client_ip = inet_ntoa(clientaddr.sin_addr);
                

                // b、处理客服端发来的数据请求(指令执行)：读取指令、子进程执行、结果返回
                //////////////////////////////////////////////////////////////////

                // b-1：检测读取到的指令是否为rm系列
                if (strcasestr(server_buffer, "rm") != nullptr || strcasestr(server_buffer, "rmdir") != nullptr)
                {
                    std:: string respond = "Wrong:该系列指令无效处理!\n";
                    std::cout<< respond << "---> 客户端输入指令为: " << server_buffer << std::endl;

                    sendto(sock_,respond.c_str(), respond.size(), 0, (struct sockaddr *)&clientaddr, len);
                    continue;
                }
                // b-2：执行其它指令
                char result[SIZE];//从fd（popen返回值）中读取执行后的结果

                FILE* fd = popen(server_buffer,"r");//type选项只可以是读或写，不可以同时进行（详细情查看函数）
                if (fd == nullptr)// 读取失败
                {
                    logMessage(ERROR, "popen: %d-%s", errno, strerror(errno));
                    continue;
                }
                while (fgets(result, sizeof result, fd) != nullptr)
                {
                    respond += result;
                }
                pclose(fd);
                //////////////////////////////////////////////////////////////////

            }
            // 2、响应：将处理好的结果返回。
            sendto(sock_, respond.c_str(), respond.size(), 0, (struct sockaddr *)&clientaddr, len);
            
        }
    }




private:
    uint16_t port_;  // 端口号：16位的整数
    std::string ip_; // IP地址：点分十进制字符串风格
    int sock_;       // 通讯时的套接字：需要供多处使用
};
}
////////////////////////////////////////////////////////////////



#endif